/**
 * This file is part of gapro library.
 * 
 * gapro library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>
 * 
 * Copyright 2012 Vinay Penmatsa. All rights reserved.
 * 
 */
package org.stupidwhiz.gapro.core;


/**
 * 3D vector with Fraction types
 * 
 * @author Vinay Penmatsa
 * @since 1.0
 */
public class Vector3dFraction implements Cloneable {

    // x-component
    private Fraction x;

    // y-component
    private Fraction y;

    // z-component
    private Fraction z;

    /**
     * Initialzes a Vector3d with x, y, z component values of zero
     * 
     */
    public Vector3dFraction() {
        this(new Fraction(0), new Fraction(0), new Fraction(0));
    }

    /**
     * Initialzes a Vector3d with given x,y,z Fraction components
     * 
     * @param x
     *            x-component
     * @param y
     *            y-component
     * @param z
     *            z-component
     */
    public Vector3dFraction(Fraction x, Fraction y, Fraction z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * Initialzes a Vector3d with given x,y,z int components
     * 
     * @param x
     *            x-component
     * @param y
     *            y-component
     * @param z
     *            z-component
     */
    public Vector3dFraction(int x, int y, int z) {
        this.x = new Fraction(x);
        this.y = new Fraction(y);
        this.z = new Fraction(z);
    }

    /**
     * Initialzes a Vector3d with given x,y,z string components
     * 
     * @param x
     *            x-component
     * @param y
     *            y-component
     * @param z
     *            z-component
     */
    public Vector3dFraction(String x, String y, String z) {
        this.x = Fraction.createFraction(x);
        this.y = Fraction.createFraction(y);
        this.z = Fraction.createFraction(z);
    }

    /**
     * Returns the x component of the vector
     * 
     * @return the x
     */
    public Fraction getX() {
        return x;
    }

    /**
     * Returns the y component of the vector
     * 
     * @return the y
     */
    public Fraction getY() {
        return y;
    }

    /**
     * Returns the z component of the vector
     * 
     * @return the z
     */
    public Fraction getZ() {
        return z;
    }

    /**
     * Adds the given vector to this vector
     * 
     * @param v
     * @return the resulting Vector3d after addition
     */
    public Vector3dFraction add(Vector3dFraction v) {
        x.add(v.x);
        y.add(v.y);
        z.add(v.z);

        return this;
    }

    /**
     * Subtracts the given vector from this vector
     * 
     * @param v
     * @return the resulting Vector3d after subtraction
     */
    public Vector3dFraction subtract(Vector3dFraction v) {
        x.subtract(v.x);
        y.subtract(v.y);
        z.subtract(v.z);

        return this;
    }

    /**
     * Multiplies this vector with the scalar parameter
     * 
     * @param f
     */
    public Vector3dFraction multiply(int scalar) {
        Fraction f = new Fraction(scalar);
        x.multiply(f);
        y.multiply(f);
        z.multiply(f);

        return this;
    }

    /**
     * Divides this vector with the scalar parameter
     * 
     * @param f
     */
    public Vector3dFraction divide(int scalar) {
        if(scalar == 0)
            throw new IllegalArgumentException("Cannot divide with zero");
        Fraction f = new Fraction(scalar);
        x.divide(f);
        y.divide(f);
        z.divide(f);

        return this;
    }

    /**
     * Returns the magnitude of this vector
     * 
     * @param f
     */
    public double magnitude() {
        Vector3dFraction v = null;
        try {
            v = (Vector3dFraction) clone();
        } catch(CloneNotSupportedException e) {
            // This must nor happen
        }
        v.x.multiply(v.x);
        v.y.multiply(v.y);
        v.z.multiply(v.z);
        v.x.add(v.y);
        v.x.add(v.z);

        return Math.sqrt(v.x.toDouble());
    }

    /**
     * Returns the dot product of this and the given vector
     * 
     * @param f
     */
    public Fraction dot(Vector3dFraction v) {
        Vector3dFraction newV = null;
        try {
            newV = (Vector3dFraction) clone();
        } catch(CloneNotSupportedException e) {
            // This must nor happen
        }
        newV.x.multiply(v.x);
        newV.y.multiply(v.y);
        newV.z.multiply(v.z);
        newV.x.add(newV.y);
        newV.x.add(newV.z);

        return newV.x;
    }

    /**
     * Returns the cross product of this and the given vector
     * 
     * @param v
     *            the vector to cross with
     */
    public Vector3dFraction cross(Vector3dFraction v) {
        return new Vector3dFraction(getCrossPart(1, v), getCrossPart(2, v), getCrossPart(3, v));
    }

    private Fraction getCrossPart(int index, Vector3dFraction v) {
        Vector3dFraction v1 = null, v2 = null;
        try {
            v1 = (Vector3dFraction) clone();
            v2 = (Vector3dFraction) clone();
        } catch(CloneNotSupportedException e) {
            // This must nor happen
        }

        switch(index) {
        case 1:
            v1.y.multiply(v.z);
            v2.z.multiply(v.y);
            v1.y.subtract(v2.z);
            return v1.y;
        case 2:
            v1.z.multiply(v.x);
            v2.x.multiply(v.z);
            v1.z.subtract(v2.x);
            return v1.z;
        case 3:
            v1.x.multiply(v.y);
            v2.y.multiply(v.x);
            v1.x.subtract(v2.y);
            return v1.x;
        default:
            break;
        }

        return null;
    }

    @Override
    public String toString() {
        return "<" + x + "," + y + "," + z + ">";
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((x == null) ? 0 : x.hashCode());
        result = prime * result + ((y == null) ? 0 : y.hashCode());
        result = prime * result + ((z == null) ? 0 : z.hashCode());
        return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        if(!(obj instanceof Vector3dFraction)) {
            return false;
        }
        Vector3dFraction other = (Vector3dFraction) obj;
        if(x == null) {
            if(other.x != null) {
                return false;
            }
        } else if(!x.equals(other.x)) {
            return false;
        }
        if(y == null) {
            if(other.y != null) {
                return false;
            }
        } else if(!y.equals(other.y)) {
            return false;
        }
        if(z == null) {
            if(other.z != null) {
                return false;
            }
        } else if(!z.equals(other.z)) {
            return false;
        }
        return true;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Vector3dFraction cloned = (Vector3dFraction) super.clone();
        cloned.x = (Fraction) x.clone();
        cloned.y = (Fraction) y.clone();
        cloned.z = (Fraction) z.clone();

        return cloned;
    }
}
